<?php


namespace EchoPHP\Foundation\BootStrap;

use EchoPHP\Support\Contacts\Debug\ExceptionHandler;
use Exception;
use ErrorException;
use EchoPHP\Foundation\Application;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\FatalThrowableError;

/**
 * 异常处理程序注册
 * Class HandleException
 * @package EchoPHP\Foundation\BootStrap
 */
class HandleException {

    /**
     * 应用程序实例
     * @var Application
     */
    protected $app;

    public function bootstrap(Application $app) {
        $this->app = $app;
        //根据环境设置，线上关闭错误显示，线下打开错误信息显示
        if (env('APP_DEBUG', false)) {
            $reportLevel = -1;
            ini_set('display_errors', 'On');
        } else {
            $reportLevel = E_ALL ^ E_WARNING ^ E_NOTICE;
            ini_set('display_errors', 'On');
        }

        error_reporting($reportLevel);
        set_error_handler([$this, 'handleError']);
        set_exception_handler([$this, 'handleException']);
        register_shutdown_function([$this, 'handleShutdown']);
    }

    /**
     * 将PHP错误信息转换为ErrorException实例,这样就可以通过异常处理流程来处理错误信息
     * @param $level
     * @param $message
     * @param string $file
     * @param int $line
     * @param array $context
     * @throws ErrorException
     */
    public function handleError($level, $message, $file = '', $line = 0, $context = []) {
        if (error_reporting() & $level) {
            throw new ErrorException($message, 0, $level, $file, $line);
        }
    }

    /**
     * 异常处理，报告错误信息并显示
     * @param $e
     * @throws Exception
     */
    public function handleException($e) {
        if (!$e instanceof Exception) {
            $e = new FatalThrowableError($e);
        }

        try {
            $this->getExceptionHandler()->report($e);
        } catch (Exception $e) {

        }

        if ($this->app->runningInConsole()) {
            $this->renderForConsole($e);
        } else {
            $this->renderForHttp($e);
        }
    }

    /**
     * 获取异常处理函数
     * @return ExceptionHandler
     * @throws Exception
     */
    protected function getExceptionHandler() {
        return $this->app->make(ExceptionHandler::class);
    }

    /**
     * 将异常信息渲染输出到控制台
     * @param Exception $e
     * @throws Exception
     */
    protected function renderForConsole(Exception $e) {
        $this->getExceptionHandler()->renderForConsole(new ConsoleOutput(), $e);
    }

    /**
     * 将异常信息渲染为http响应并输出
     * @param Exception $e
     * @throws Exception
     */
    protected function renderForHttp(Exception $e) {
        $this->getExceptionHandler()->render($this->app['request'], $e)->send();
    }

    /**
     * 脚本结束处理程序，如果是致命错误引起的脚本终端，那么抓获错误信息并转入异常处理流程
     * @throws Exception
     */
    public function handleShutdown() {
        if (!is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
            $this->handleException($this->fatalExceptionFromError($error, 0));
        }
    }

    /**
     * 判断错误类型是否是致命错误
     * @param $type
     * @return bool
     */
    protected function isFatal($type) {
        return in_array($type, [E_COMPILE_ERROR, E_CORE_ERROR, E_ERROR, E_PARSE]);
    }

    /**
     * 将致命错误转换为一个异常用于后续处理
     * @param array $error
     * @param null $traceOffset
     * @return FatalErrorException
     */
    protected function fatalExceptionFromError(array $error, $traceOffset = null) {
        return new FatalErrorException(
            $error['message'], $error['type'], 0, $error['file'], $error['line'], $traceOffset
        );
    }
}